
#ifndef INC_CAMERA_H
#define INC_CAMERA_H

#include "Vector3.h"
#include "Plane.h"
#include "Matrix44.h"
#include "Mathutil.h"

//for std::min and std::max
#include <algorithm>

namespace gti
{

class AABB;

class Camera  
{
public:
    enum ProjectionType
	{
		PERSPECTIVE = 0,
		ORTHOGRAPHIC,
		FRUSTUM,
	};

	Camera();
// camera projection
	void setProjectionType(ProjectionType p);
	Camera::ProjectionType getProjectionType() const;

// view and projection matrix methods
	void perspective();  // replace graphics hardware matrices with the new ones
	void ortho(); // replace graphics hardware matrices with the new ones

// replace matrices of the fixed pipeline with the current view and projection
	void set();
	void compute(); //update matrices

	// adjust the view and projection matrix parameters
	void lookAt(const vector3f& eye,const vector3f& center,const vector3f& up);
	void lookAt(const vector3f& eye,const quaternion& orientation);

	void lookAt(const vector3f& eye, CubemapFace face);

	const vector3f& getEyePoint() const;
	const vector3f& getCenterPoint() const;
	const vector3f& getUpVector() const;
	vector3f getForwardVector() const;
	void setEyePoint(const vector3f& newEye);
	void setCenterPoint(const vector3f& newCenter);
	void updateUpVector();

	const matrix44& getViewMatrix() const;
	const matrix44& getProjectionMatrix() const;
	const matrix44& getViewProjectionMatrix() const;

	// adjust the near and far clipping planes
	float getNearDistance() const;
	float getFarDistance() const;

	void setNearDistance(float distance);
	void setFarDistance(float distance);

	void setCameraPerspective(float fov, float aspect, float near, float far);
	void setCameraOrthogonal(float left, float right, float bottom, float top, float near, float far);
	void setCameraMatrices(const matrix44& view, const matrix44& projection);

	// aspect ratio and field of view parameters (for perspective projections)
	void setAspect(float aspect);
	float getAspect() const;
	void setFov(float fov);
	float getFov() const;

	// manual adjust of the horizontal and vertical clipping planes (for ortho projections)
	void getClippingPlanes(float& left, float& right, float& bottom, float& top) const;
	void setClippingPlanes(float left, float right, float bottom, float top);

// transformations
	// go forward toward direction of projection
	void forward(float meters);
	// go forward in perpendicular direction (right vector) to the direction of projection
	void strafeRight(float meters);
	// go forward in perpendicular direction (up vector) to the direction of projection
	void strafeUp(float meters);
	// translate the eye point by vector t 
	void translateEye(const vector3f& t);
	// translate the center point by vector t 
	void translateCenter(const vector3f& t);
	// translate the eye and center points by vector t 
	void translate(const vector3f& t);
	
	
// rotate keeping the eye point fixed
	void turn(const vector3f& v, float radians);
	// performs a turn arround the up_vector
	// convenience method: equivalent to turn(up_vector,radians)
	void yaw(float radians);
	// performs a turn arround the camera_right_vector
	// convenience method: equivalent to turn(camera_right_vector,radians)
	void pitch(float radians);	
	// performs a turn arround the direction of projection
	// convenience method: equivalent to turn(camera_lookat_vector,radians)
	void roll(float radians);

// rotate looking always to the center point (keeeping the center point)
	void turnCenter(const vector3f& v, float radians);
	void yawCenter(float radians);
	void pitchCenter(float radians);	
	void rollCenter(float radians);	

	// rotate arround a point
	// p = point
	// v = axis of rotation
	// radians = angle of rotation arround axis (in radians)
	void turnPoint(const gti::vector3f& p, const gti::vector3f& v, float radians);
	void pitchPoint(const gti::vector3f& p, float radians);

// useful methods
	// adjusts the camera to fit the bounding sphere without changing the field of view 
	void viewAll(const vector3f& sphereCenter, float radius);
	// adjusts the camera to fit the aabb without changing the field of view 
	void viewAll(const AABB& aabb);

	// adjust the fov in perspective projections (non-sense in orthographic projections)
	// zoom(4) performs a ~45 fov;
	void zoom(float diameter); // diameter in meters
	// adjusts the camera to fit the bounding sphere changing the field of view 
	void zoom(const vector3f& sphereCenter, float radius);
	// adjusts the camera to fit the boxes changing the field of view 
	void zoom(const AABB& aabb);

	// enlarge and shrink ortographic projections (non-sense in perspective projections)
	void shrink(float percent); //positive values enlarge and negative shrink
	// adjusts the camera to fit the bounding sphere in ortographic projections
	void shrink(const vector3f& sphereCenter, float radius);
	// adjusts the camera to fit the objects in orthographic projections
	void shrink(const AABB& aabb);

	// move back the eyePoint readjusting the near and far planes and the fov
	// (for perspective shadow maps)
	void virtualSlide(float offset);

// frustum methods
	void renderFrustum() const;

		
// frustum culling methods
	void updateFrustumMatrixFromViewport(float* client_viewport, bool normalized = true);

	CullFlag inside(const vector3f& v) const;
	// camera aabb overlap test
	CullFlag overlap(const AABB& aabb) const;
	// camera sphere overlap test
	CullFlag overlap(const vector3f& center, float radius) const;

	// screen space methods
	// use normalized values (between 0-1)
	void setViewport(float left, float bottom, float width, float height);
	// use screen values (in pixels)
	void setViewportNormalized(float left, float bottom, float width, float height);

	float* getViewport() { return viewport; }
	void getViewport(float* v);
	void getViewportNormalized(float* v);

	// compute the 2d screen coordinate of a 3d point
	void project(const vector3f& point, float& x, float& y, float& z) const;

  // Return the direction in world space from the eye point going throw the normalized ( 0,0 ) .. ( 1,1 ) space of the view port
  vector3f getRayDirection( float normalized_x_from_left_to_right, float normalized_y_from_top_to_bottom ) const;

	// It computes the world coordinates of a pixel in screen coordinates
	// Params:
	// z should be in range 0..1 (0 = z_near point and 1 = zfar point)
	void unproject(float xScreen, float yScreen, float zScreen, vector3f& point) const;
	float computeProjectedArea(const AABB& box) const;

// frustum planes
	const Plane& left() const;
	const Plane& right() const;
	const Plane& top() const;
	const Plane& bottom() const;
	const Plane& front() const;
	const Plane& back() const;

	const Plane* getPlanes() const;

	// extract planes from matrix vp
	static void computeViewMatrix(const vector3f& eye,const vector3f& center,const vector3f& up, matrix44& view);
	static void extractFrustumPlanes(const matrix44& vp, Plane* planes);
	static CullFlag overlap(const Plane* planes, const AABB& box);

	static void setApplicationWindowSize(int width, int height);
	static int getApplicationWindowWidth() { return window_width; }
	static int getApplicationWindowHeight() { return window_height; }

protected:

	ProjectionType projection_type;
// camera view look at members
	vector3f eye_point,center_point,up_vector;
// perspective and ortho members
	float perspective_fov; // fov (in angles)
	float perspective_aspect; 
	float ortho_left,ortho_right,ortho_bottom,ortho_top;
	float z_near,z_far;

// view, projection and MVP matrices
	matrix44 view_matrix, projection_matrix, view_projection_matrix;
// viewport information
	float viewport[4];

// frustum planes
	Plane planes[6];

// frustum custom parameters
	/* DEPRECATED
	unsigned int node_id[2];
	unsigned int number_of_nodes[2];
	unsigned int margin_pixels;
	*/

	static int window_width;
	static int window_height;

//protected: // EGB
	// compute the look at vectors from the view matrix
	void computeLookAtVectorsFromViewMatrix(vector3f& eye, vector3f& center, vector3f& up) const;
	
	// computes the view matrix from eye, center and up vector
	void computeViewMatrix();
	
	// compute the projection matrix based on projection type
	void computeProjectionMatrix();
	
	// compute the Frustum Matrix (for frustum projections)
	void computeFrustumMatrix();

	// compute the Frustum Matrix (for perspective projections)
	void computePerspectiveMatrix();
	
	// compute the Frustum Matrix (for orthographic projections)
	void computeOrthographicMatrix();
	
	// compute the view projection matrix and the frustum planes
	
	void computeViewProjectionMatrix();
	
	// compute the frustum planes from VP matrix
	void computeFrustumPlanes();

	//serialization TODO
    //void* serialize(size_t& size);
	//void unserialize(void* data, size_t size);
};

}//namespace gti

#endif //INC_CAMERA_H
